/*
 * utilities class for Driver.java
 */
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;

import magick.ImageInfo;
import magick.MagickException;
import magick.MagickImage;

public class Utilities {

	// CONSTANTS
	private static final String OUT_ORIGINAL_BIN = "out/original.bin";
	private static final String OUT_IMAGE_FILENAME = "out/output.jpg";
	public static final String OUT_OBJECT_RGB_FILENAME = "out/out.RGB";
	public static final String OUT_OBJECT_YBR_FILENAME = "out/out.YBR";

	// Loaded image information
	public static String _imageLocation = null;
	public static Image _loadedImage = null;

	public static int _width;
	public static int _height;
	public static byte[] _dispatched = null;
	public static int _reducedWidth;
	public static int _reducedHeight;
	public static byte[] _reducedDispatch = null;

	// parameters
	public static int _s1;
	public static int _s2;
	public static int _s3;
	public static int _n1;
	public static int _n2;
	public static int _n3;
	public static int _m;
	public static int _d;
	public static int _colorspace;

	// task config
	public static int _task2;
	public static int _task3;
	public static int _task4;
	public static int _task5;

	// the symbol table instance & image instance for funcs
	private static final SymbolTable _symbolTable = new SymbolTable();
	private static final Image _imgInstance = new Image();
	
	public static byte[] orig;
	public static byte[] pcsignsr, pcsignsg, pcsignsb;

	public static String showInputImage(ActionEvent e, String task) {

		String out = null;

		if (e.getActionCommand().equals("SHOW_INPUTIMAGE")) {
			if (_loadedImage != null) {
				UtilFuncs.displayImage(_loadedImage.getInstance(), task);
				out = "done";
			}
		}

		return out;
	}

	public static String open(ActionEvent e) {

		String out = null;

		if (e.getActionCommand().equals("OPEN")) {

			JFileChooser fileChooser = new JFileChooser(".");
			FileFilter filter = new ExtensionFileFilter("JPG,JPEG,BMP,GIF,TIFF,PNG", new String[] { "JPG", "JPEG",
					"BMP", "GIF", "TIFF", "PNG" });
			fileChooser.setFileFilter(filter);

			int status = fileChooser.showOpenDialog(null);

			if (status == JFileChooser.APPROVE_OPTION) {
				out = fileChooser.getSelectedFile().getPath();
				_imageLocation = out;

				_loadedImage = new Image(_imageLocation);
				_loadedImage.dispatch();

				_width = _loadedImage.getW();
				_height = _loadedImage.getH();
				
				byte[] red = _loadedImage.getLayer(0);
				byte[] green = _loadedImage.getLayer(1);
				byte[] blue = _loadedImage.getLayer(2);

				_dispatched = new byte[_width * _height * 3];
				for (int i = 0; i < red.length; i++) {
					_dispatched[3 * i] = red[i];
					_dispatched[3 * i + 1] = green[i];
					_dispatched[3 * i + 2] = blue[i];
				}
			}
		}

		return out;
	}

	public static String encode() {
		if (_loadedImage == null) return "Error. Load an image first.";

		// write out the basic uncompressed file for reference.
		UtilFuncs.writeToFile(OUT_ORIGINAL_BIN, _dispatched);

		byte[] dispatched = _loadedImage.getDispatched();
		byte[] red = _loadedImage.getLayer(0);
		byte[] green = _loadedImage.getLayer(1);
		byte[] blue = _loadedImage.getLayer(2);

		// Color Space Conversion
		if (_colorspace > 0) {
			
			byte[] extendedYbr = new byte[(_dispatched.length / 3) * 4];
			for (int i = 0; i < _dispatched.length / 3; i++) {
				// convert RGB to YCbCr
				double[] currentconv = _imgInstance.ycbcr(_dispatched[3 * i] & 0xFF,
						_dispatched[3 * i + 1] & 0xFF,
						_dispatched[3 * i + 2] & 0xFF);

				extendedYbr[4 * i] = (byte) currentconv[0];
				extendedYbr[4 * i + 1] = (byte) currentconv[1];
				extendedYbr[4 * i + 2] = (byte) currentconv[2];
				extendedYbr[4 * i + 3] = 1;
			}

			dispatched = extendedYbr;
		}
		
		orig = new byte[dispatched.length * 3 / 4];
		for(int i = 0; i < dispatched.length / 4; i++)
		{
			orig[3*i] = dispatched[4*i];
			orig[3*i+1] = dispatched[4*i+1];
			orig[3*i+2] = dispatched[4*i+2];
		}

		
		// TASK 1 - Resolution Reduction
		int minS = (_s1 <= _s2 && _s1 <= _s3) ? _s1 : ((_s2 <= _s1 && _s2 <= _s3) ? _s2 : _s3);
		if (minS > 1) {
			red = ResolutionReduction.reduceResolution(dispatched, _width, _height, minS, 0);
			green = ResolutionReduction.reduceResolution(dispatched, _width, _height, minS, 1);
			blue = ResolutionReduction.reduceResolution(dispatched, _width, _height, minS, 2);
		}

		_reducedWidth = _width / minS;
		_reducedHeight = _height / minS;

		_reducedDispatch = new byte[red.length * 3];
		for (int i = 0; i < red.length; i++) {
			_reducedDispatch[3 * i] = red[i];
			_reducedDispatch[3 * i + 1] = green[i];
			_reducedDispatch[3 * i + 2] = blue[i];
		}

		// TASK 2 - Quantization
		List<Entry> redSymbols = null;
		List<Entry> greenSymbols = null;
		List<Entry> blueSymbols = null;

		if (_task2 > 0 && _n1 > 0 && _n2 > 0 && _n3 > 0) {
			redSymbols = UtilFuncs.getQuantizedSymbols(red, _n1, 256);
			greenSymbols = UtilFuncs.getQuantizedSymbols(green, _n2, 256);
			blueSymbols = UtilFuncs.getQuantizedSymbols(blue, _n3, 256);

			// update the component values with the bin indices
			red = UtilFuncs.getByteArray(redSymbols);
			green = UtilFuncs.getByteArray(greenSymbols);
			blue = UtilFuncs.getByteArray(blueSymbols);
		}

		// TASK 3 - Predictive Coding
		if (_task3 > 0) {
			int[] intRed = Image.correctedPixels(red);
			int[] pcr = Image.applyPredictiveCoding(_task3 + 1, intRed, _reducedWidth);

			int[] intGreen = Image.correctedPixels(green);
			int[] pcg = Image.applyPredictiveCoding(_task3 + 1, intGreen, _reducedWidth);

			int[] intBlue = Image.correctedPixels(blue);
			int[] pcb = Image.applyPredictiveCoding(_task3 + 1, intBlue, _reducedWidth);

			// TODO some big conceptual issue, where the int value range can be from -255 to 255!

			// update the components with new predicted values
			pcsignsr = new byte[pcr.length];
			pcsignsg = new byte[pcg.length];
			pcsignsb = new byte[pcb.length];
			for (int i = 0; i < intRed.length; i++) {
				if(pcr[i] < 0)
				{
					pcr[i] = -pcr[i];
					pcsignsr[i] = 1;
				}
				else
				{
					pcsignsr[i] = 0;
				}
				red[i] = (byte) pcr[i];
				if(pcg[i] < 0)
				{
					pcg[i] = -pcg[i];
					pcsignsg[i] = 1;
				}
				else
				{
					pcsignsg[i] = 0;
				}
				green[i] = (byte) pcg[i];
				if(pcb[i] < 0)
				{
					pcb[i] = -pcb[i];
					pcsignsb[i] = 1;
				}
				else
				{
					pcsignsb[i] = 0;
				}
				blue[i] = (byte) pcb[i];
			}

			// update the entry list with the predicted values
			if (redSymbols != null && _task4 == 0) {
				redSymbols = UtilFuncs.getEntryList(red, _n1);
				greenSymbols = UtilFuncs.getEntryList(green, _n2);
				blueSymbols = UtilFuncs.getEntryList(blue, _n3);
			}
		}

		// TASK 4 - Error Quantization
		if (_task4 > 0) {
			redSymbols = UtilFuncs.getQuantizedSymbols(red, _m, _task2 > 0? _n1: 256);
			greenSymbols = UtilFuncs.getQuantizedSymbols(green, _m, _task2 > 0? _n2: 256);
			blueSymbols = UtilFuncs.getQuantizedSymbols(blue, _m, _task2 > 0? _n3: 256);

			// update the component values with the bin indices
			red = UtilFuncs.getByteArray(redSymbols);
			green = UtilFuncs.getByteArray(greenSymbols);
			blue = UtilFuncs.getByteArray(blueSymbols);
		}

		// TASK 5 - Variable length encoding (Shannon-Fano or LZW)
		byte[] combined = new byte[red.length * 3];
		for (int i = 0; i < red.length; i++) {
			combined[3 * i] = red[i];
			combined[3 * i + 1] = green[i];
			combined[3 * i + 2] = blue[i];
		}

		if (_task5 == 0) {
			// No VLC selected, write out using the entry list or the byte array
			String fileName;
			if (_colorspace == 0) {
				fileName = OUT_OBJECT_RGB_FILENAME;
			} else {
				fileName = OUT_OBJECT_YBR_FILENAME;
			}
			// if quantization is done
			if (redSymbols != null) {
				// combine the RGB as single entry list before writing
				List<Entry> combinedEntries = new ArrayList<Entry>();
				for (int i = 0; i < redSymbols.size(); i++) {
					combinedEntries.add(redSymbols.get(i));
					combinedEntries.add(greenSymbols.get(i));
					combinedEntries.add(blueSymbols.get(i));
				}
				UtilFuncs.writeToFile(fileName, combinedEntries);
			} else {
				// no quantization, write out the combined byte sequence
				UtilFuncs.writeToFile(fileName, combined);
			}
		// TODO the delegated file writes use a different file location, fix that
		} else if (_task5 == 1) {
			// Shannon-Fano selected, delegate writing out to SymbolTable
			_symbolTable.buildTableViaSF(combined);
			_symbolTable.writeOut(combined, _colorspace);
		} else {
			// LZW selected, delegate writing out to LZW
			LZW.writeOut(combined, _d, _colorspace);
		}

		return "Successfully encoded file.";
	}

	public static String decode() {
		byte[] decoded;
		byte[] red, green, blue;

		String fileName;
		if (_colorspace == 0) {
			fileName = OUT_OBJECT_RGB_FILENAME;
		} else {
			fileName = OUT_OBJECT_YBR_FILENAME;
		}

		// read the binary file according to how it was encoded
		if (_task5 == 0) {
			if (_task4 > 0) {
				decoded = UtilFuncs.getByteArray(UtilFuncs.readFromFile(fileName, _m, _m, _m));
			} else if (_task2 > 0) {
				decoded = UtilFuncs.getByteArray(UtilFuncs.readFromFile(fileName, _n1, _n2, _n3));
			} else {
				decoded = UtilFuncs.getByteArray(UtilFuncs.readFromFile(fileName, 256, 256, 256));
			}
		} else if (_task5 == 1) {
			decoded = _symbolTable.readIn(_colorspace);
		} else {
			decoded = LZW.readIn(_colorspace);
		}

		int sml = decoded.length / 3;

		red = new byte[sml];
		green = new byte[sml];
		blue = new byte[sml];

		for (int i = 0; i < red.length; i++) {
			red[i] = decoded[3 * i];
			green[i] = decoded[3 * i + 1];
			blue[i] = decoded[3 * i + 2];
		}

		// TODO de-quantize errors if at all they were quantized
		if (_task4 > 0) {
			for (int i = 0; i < red.length; i++) {
				decoded[3 * i] = (byte) ((int)(red[i] & 0xff) * (int)((_task2 > 0? _n1:256) / _m));
				decoded[3 * i + 1] = (byte) ((int)(green[i] & 0xff) * (int)((_task2 > 0? _n2:256) / _m));
				decoded[3 * i + 2] = (byte) ((int)(blue[i] & 0xff) * (int)((_task2 > 0? _n3:256) / _m));
				red[i] = decoded[3 * i];
				green[i] = decoded[3 * i + 1];
				blue[i] = decoded[3 * i + 2];
			}
		}

		// remove predic coding
		if (_task3 > 0) {

			int[] int_red = Image.correctedPixels(red);
			for(int i = 0; i < int_red.length; i++)
			{
				if(pcsignsr[i] == 1)
					int_red[i] = -int_red[i];
			}
			int[] pcr = Image.removePredictiveCoding(_task3 + 1, int_red, _reducedWidth);

			int[] int_green = Image.correctedPixels(green);
			for(int i = 0; i < int_green.length; i++)
			{
				if(pcsignsg[i] == 1)
					int_green[i] = -int_green[i];
			}
			int[] pcg = Image.removePredictiveCoding(_task3 + 1, int_green, _reducedWidth);

			int[] int_blue = Image.correctedPixels(blue);
			for(int i = 0; i < int_blue.length; i++)
			{
				if(pcsignsb[i] == 1)
					int_blue[i] = -int_blue[i];
			}
			int[] pcb = Image.removePredictiveCoding(_task3 + 1, int_blue, _reducedWidth);

			for (int i = 0; i < int_red.length; i++) {
				red[i] = (byte) pcr[i];
				green[i] = (byte) pcg[i];
				blue[i] = (byte) pcb[i];
			}

		}

		// restore bin indices with their values (de-quantization)
		for (int i = 0; i < red.length; i++) {
			decoded[3 * i] = (byte) (_task2 > 0 ? ((int)(red[i] & 0xff) * (int)(256 / _n1)) : red[i]);
			decoded[3 * i + 1] = (byte) (_task2 > 0 ? ((int)(green[i] & 0xff) * (int)(256 / _n2)) : green[i]);
			decoded[3 * i + 2] = (byte) (_task2 > 0 ? ((int)(blue[i] & 0xff) * (int)(256 / _n3)) : blue[i]);
		}

		// write out the final image and display it
		try {
			MagickImage image = new MagickImage();
			image.constituteImage(_reducedWidth, _reducedHeight, "RGB", decoded);
			image.setFileName(OUT_IMAGE_FILENAME);
			image.writeImage(new ImageInfo());
			UtilFuncs.displayImage(image, OUT_IMAGE_FILENAME);
		} catch (MagickException e) {
			e.printStackTrace();
		}

		// display and output size/SNR.

		return "Image decompression complete.\n" + "Original Size: " + UtilFuncs.getSize(OUT_ORIGINAL_BIN) + " bytes.\n"
				+ "Compressed Size: " + UtilFuncs.getSize(fileName) + " bytes.\n" + "Compression Ratio: "
				+ (UtilFuncs.getSize(fileName) * 1.0) / UtilFuncs.getSize(OUT_ORIGINAL_BIN) + "\n" + "SNR: "
				+ Image.getDistortion(_reducedDispatch, decoded);
	}

}
